/**
 * 
 */
package wordEvo.client;

import java.io.IOException;
import java.net.UnknownHostException;

import whipstaff.data.WhipPacket;
import whipstaff.tools.IOHelper;
import whipstaff.tools.OutputLog;
import whipstaff.tools.PrintHelper;
import whipstaff.tools.TimeHelper;
import whipstaff.wordevo.data.Request;
import whipstaff.wordevo.data.Result;
import whipstaff.wordevo.lists.ChildList;
import whipstaff.wordevo.words.ChildWord;
import whipstaff.wordevo.words.GoalWord;

/**
 * @author Jim Buck, Whipstaff Productions
 * @since August 9, 2011
 * @updated September 10, 2011
 */
public class ClientThread extends Thread implements Comparable
{
	private OutputLog log;
	
	private String address;
	private int port;
	
	private GoalWord goalWord;
	private ChildList children;
	
	private long instances;
	private long maxChildren;
	
	private long startTime;
	private long endTime;
	private long estRunTime;
	
	private long totalTrials;
	private long totalGens;
	private long totalChildren;
	private long totalNanos;
	private long charCount[];
	
	private Result result;
	private EvoClient parent;
	
	// ***************** Constructors, Getters, and Setters ***********************
	
	// Basic Constructor
	public ClientThread(EvoClient evo)
	{
		this.parent = evo;
		this.log = evo.getLog();
		this.address = evo.getAddress();
		this.port = evo.getResultPort();
		
		this.goalWord = null;
		this.children = new ChildList();
		
		this.instances = -1;
		this.maxChildren = -1;
		
		this.startTime = -1;
		this.endTime = -1;
		
		this.totalTrials = -1;
		this.totalGens = -1;
		this.totalChildren = -1;
		this.totalNanos = -1;
		this.charCount = null;
		this.estRunTime = -1;
	}
	
	/*/ Sets the goal word to test the fitness against.	
	public void setGoalWord(GoalWord goalWord) {
		this.goalWord = goalWord;
	}
	// Sets the total number of trials to perform.
	public void setInstances(long instances) {
		this.instances = instances;
	}
	// Sets the maximum children per trial.
	public void setMaxChildren(long maxChildren) {
		this.maxChildren = maxChildren;
	}//*/
	
	// Specifies a port for connecting to.
	public void setPort(int addr) {
		this.port = addr;
	}
	
	// ********************** Primary Execution Methods ***************************
	
	public void define(Request req) {
		this.define(req.getGoalWord(), req.getInstances(), req.getMaxChildren());
	}
	
	// Called by EvoClient to initialize the simulation of this thread.
	public void define(GoalWord w, long i, long c) {
		
		this.goalWord = w;
		this.charCount = new long[w.getAlpha().length()];
		
		this.instances = i;
		this.maxChildren = c;
		
		this.estRunTime = w.toString().length() * i * c;
	}
	
	// Ran when called by newSimulation
	@Override
	public void run() {
		initSimulation();
		runTrials();
		generateResult();
		transmitResult();
	}

	// *********************** Primary Execution Sequence ***************************
	
	// Initializes the statistics for recording.
	private void initSimulation() {
		this.totalTrials = 0;
		this.totalGens = 0;
		this.totalChildren = 0;
		this.totalNanos = 0;
	}
	// Initiates and runs all of the trials.
	private void runTrials() {
		for(int index = 0; index < this.instances*this.goalWord.getFreq(); index++) {
			beginRecording();
			runSimulation();
			finishRecording();
		}
		//System.gc();
	}
	// Records the current System time.
	private void beginRecording() {
		this.startTime = System.nanoTime();
	}
	// Runs the calculations and stores the data.
	private void runSimulation() {
		ChildWord guess = new ChildWord(this.goalWord);
				
		while(!this.goalWord.isSameString(guess))
		{
			this.children.clear();
			
			for(int i = 0; i < this.maxChildren; i++)
			{
				ChildWord newChild = new ChildWord(guess);
				children.add(newChild);
				
				if(newChild.getFitness() == 0)
				{
					//totalChildren += maxChildren - i;
					break;
				}
				else {
					totalChildren++;
				}
			}
			
			this.totalGens++;
			
			guess = children.getBestChild();
			charCount[(int) guess.getLetterIndex()]++;
		}
		
	}
	// Stop time and calculate the total elapsed nanoseconds.
	private void finishRecording() {
		this.endTime = System.nanoTime();
		this.totalNanos += this.endTime - this.startTime;
		
		this.totalTrials++;
	}
	// Create a new Result based on the calculated statistics.
	private void generateResult() {
		this.result = new Result(this.goalWord,
								 this.instances,
								 this.maxChildren,
								 this.totalGens,
								 this.totalChildren,
								 this.totalNanos,
								 this.totalTrials,
								 this.charCount);
		
		if(this.goalWord.toString().length() < 4) {
			this.log.println(this.goalWord + " (" + this.goalWord.getFreq() + ")" + 
					"\t\t| Time: " + TimeHelper.printTime(this.totalNanos) + 
					"\t| Children: " + PrintHelper.formattedLong(this.totalChildren) + 
					"\t| Generations: " + PrintHelper.formattedLong(this.totalGens));
		} else {
			this.log.println(this.goalWord + " (" + this.goalWord.getFreq() + ")" + 
							"\t| Time: " + TimeHelper.printTime(this.totalNanos) + 
							"\t| Children: " + PrintHelper.formattedLong(this.totalChildren) + 
							"\t| Generations: " + PrintHelper.formattedLong(this.totalGens));
		}
	}
	// Sends Result to WSP to add to the database.
	private void transmitResult() {
		try {
			if(this.parent.serverIsAvailable()) {			
				WhipPacket packet = new WhipPacket(this.result, "./wordevo/results", "", ".result");
				IOHelper.transmitObject(this.address, this.port, packet);
			}
			else {
				this.parent.saveForLater(this.result);
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			this.log.printerr("The server(" + this.address + ":" + this.port + ") could not be reached. Saving result to local filesystem...");
			this.parent.setConnectionStatus(false);
			this.parent.saveForLater(this.result);
		}
	}

	// *********************** Simulation Helper Methods *****************************
	
	public void printStats() {
		// Output to System
		System.out.println("**********************");

		System.out.println("Goal Word: " + this.goalWord.toString());
		System.out.println("Total Trials: " + totalTrials);
		System.out.println("Total Time: " + this.totalNanos / Math.pow(10, 9) + "\n");
	}
	
	public int compareTo(ClientThread that) {
		return (this.goalWord.toString().length() - that.goalWord.toString().length());
	}
	
	@Override
	public String toString() {
		return "ClientThread: " + super.getId() + " | GoalWord: " + this.goalWord.toString() + " | " + (this.isAlive()?"Alive":"Dead");
		
	}

	@Override
	public int compareTo(Object obj) {
		ClientThread that = (ClientThread) obj;
		
		return (int) (that.estRunTime - this.estRunTime);
	}
}
